package com.tianji.learning.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.SignMessage;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.service.SignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;

@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements SignRecordService {
    private final RedisTemplate redisTemplate;
    private String SIGN_KEY_PREFIX = "sign:uid:";
    private final RabbitMqHelper mqHelper;


    /**
     * 签到
     *
     * @return
     */
    @Override
    public SignResultVO sign() {
        // 1.签到
        // 1.1 获取签到key
        String key = getSignKey();
        // 1.2 计算offset值
        int offset = getDayOfMonth() - 1;
        // 是否存在签到key
        Boolean hassedKey = redisTemplate.hasKey(key);
        // 1.3 签到并验证是否重复签到
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (!hassedKey){
            // 1.4 如果是第一次签到，给key设置过期时间，到下月初过期
            LocalDateTime beginTime = LocalDateTime.now();
            LocalDateTime endTime = DateUtils.getMonthBeginTime(LocalDate.now().plusMonths(1));
            Duration duration = DateUtils.between(beginTime, endTime);
            redisTemplate.expire(key, duration);
        }
        if (BooleanUtil.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到");
        };

        // 2.计算连续签到天数
        int days = countSignDays();

        // 3.计算连续签到奖励积分
        int rewardPoints = computeRewardPoints(days);


        // 4.封装vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(days);
        vo.setRewardPoints(rewardPoints);

        // 5.发送消息保存积分明细记录
        try {
            mqHelper.send(LEARNING_EXCHANGE,SIGN_IN, SignMessage.of(UserContext.getUser(),vo.totalPoints()));
        } catch (Exception e) {
            log.error("签到发送保存积分明细消息失败", e);
        }
        // 6.返回vo

        return null;
    }

    /**
     * 查询签到记录
     *
     * @return
     */
    @Override
    public Byte[] querySignRecords() {

        return getBytesOfSignRecords();
    }

    /**
     * 查询签到记录转Byte数组
     * @return
     */
    private Byte[] getBytesOfSignRecords() {
        // 1.通过bitfield命令获取本月从第一天到今天为止的所有签到记录
        String key = getSignKey();
        List<Long> result = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(getDayOfMonth())).valueAt(0));

        if (ObjectUtil.isEmpty(result)){
            return new Byte[]{};
        }
        int signResult = result.get(0).intValue();

        Byte[] arr = new Byte[getDayOfMonth()];

        // 循环每一天的签到记录，赋值给Byte数组， 倒着取，倒着赋值
        int pos = getDayOfMonth() - 1;
        while (pos >= 0){
            arr[pos--] = (byte) (signResult & 1);
            // 签到结果无符号右移一位
            signResult >>>= 1;
        }
        return arr;
    }

    /**
     * 计算连续签到奖励积分
     * @param days
     * @return
     */
    private int computeRewardPoints(int days) {
        switch (days){
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;
        }
    }

    /**
     * 获取今天是当月第n天
     * @return
     */
    private static int getDayOfMonth() {
        return LocalDateTime.now().getDayOfMonth();
    }

    /**
     * 计算连续签到天数
     * @return
     */
    private int countSignDays() {
        // 1.通过bitfield命令获取本月从第一天到今天为止的所有签到记录
        String key = getSignKey();
        List<Long> result = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(getDayOfMonth())).valueAt(0));

        if (ObjectUtil.isEmpty(result)){
            return 0;
        }
        int signResult = result.get(0).intValue();

        // 记录连续签到天数
        int days = 0;
        while ((signResult & 1) == 1 ){
            // 连续签到天数 +1
            days++;
            // 签到结果无符号右移一位
            signResult >>>= 1;
        }
        return days;
    }

    /**
     * 获取当前用户在当月的签到key sign:uid:2:202503
     * @return
     */
    private String getSignKey() {
        Long userId = UserContext.getUser();

        // 获取当前日期
        String dateStr = LocalDate.now().format(DateUtils.DATE_SUFFIX_FORMATTER);


        return SIGN_KEY_PREFIX + userId + dateStr;
    }
}
